/*!
 * jQuery outside events - v1.1 - 3/16/2010
 * http://benalman.com/projects/jquery-outside-events-plugin/
 *
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */

// Script: jQuery outside events
//
// *Version: 1.1, Last updated: 3/16/2010*
//
// Project Home - http://benalman.com/projects/jquery-outside-events-plugin/
// GitHub       - http://github.com/cowboy/jquery-outside-events/
// Source       - http://github.com/cowboy/jquery-outside-events/raw/master/jquery.ba-outside-events.js
// (Minified)   - http://github.com/cowboy/jquery-outside-events/raw/master/jquery.ba-outside-events.min.js (0.9kb)
//
// About: License
//
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
//
// About: Examples
//
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
//
// clickoutside - http://benalman.com/code/projects/jquery-outside-events/examples/clickoutside/
// dblclickoutside - http://benalman.com/code/projects/jquery-outside-events/examples/dblclickoutside/
// mouseoveroutside - http://benalman.com/code/projects/jquery-outside-events/examples/mouseoveroutside/
// focusoutside - http://benalman.com/code/projects/jquery-outside-events/examples/focusoutside/
//
// About: Support and Testing
//
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
//
// jQuery Versions - 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1.
// Unit Tests      - http://benalman.com/code/projects/jquery-outside-events/unit/
//
// About: Release History
//
// 1.1 - (3/16/2010) Made "clickoutside" plugin more general, resulting in a
//       whole new plugin with more than a dozen default "outside" events and
//       a method that can be used to add new ones.
// 1.0 - (2/27/2010) Initial release
//
// Topic: Default "outside" events
//
// Note that each "outside" event is powered by an "originating" event. Only
// when the originating event is triggered on an element outside the element
// to which that outside event is bound will the bound event be triggered.
//
// Because each outside event is powered by a separate originating event,
// stopping propagation of that originating event will prevent its related
// outside event from triggering.
//
//  OUTSIDE EVENT     - ORIGINATING EVENT
//  clickoutside      - click
//  dblclickoutside   - dblclick
//  focusoutside      - focusin
//  bluroutside       - focusout
//  mousemoveoutside  - mousemove
//  mousedownoutside  - mousedown
//  mouseupoutside    - mouseup
//  mouseoveroutside  - mouseover
//  mouseoutoutside   - mouseout
//  keydownoutside    - keydown
//  keypressoutside   - keypress
//  keyupoutside      - keyup
//  changeoutside     - change
//  selectoutside     - select
//  submitoutside     - submit

(function ($, doc, outside) {
    "$:nomunge"; // Used by YUI compressor.

    $.map(
        // All these events will get an "outside" event counterpart by default.
        "click dblclick mousemove mousedown mouseup mouseover mouseout change select submit keydown keypress keyup".split(
            " "
        ),
        function (event_name) {
            jq_addOutsideEvent(event_name);
        }
    );

    // The focus and blur events are really focusin and focusout when it comes
    // to delegation, so they are a special case.
    jq_addOutsideEvent("focusin", "focus" + outside);
    jq_addOutsideEvent("focusout", "blur" + outside);

    // Method: jQuery.addOutsideEvent
    //
    // Register a new "outside" event to be with this method. Adding an outside
    // event that already exists will probably blow things up, so check the
    // <Default "outside" events> list before trying to add a new one.
    //
    // Usage:
    //
    // > jQuery.addOutsideEvent( event_name [, outside_event_name ] );
    //
    // Arguments:
    //
    //  event_name - (String) The name of the originating event that the new
    //    "outside" event will be powered by. This event can be a native or
    //    custom event, as long as it bubbles up the DOM tree.
    //  outside_event_name - (String) An optional name for the new "outside"
    //    event. If omitted, the outside event will be named whatever the
    //    value of `event_name` is plus the "outside" suffix.
    //
    // Returns:
    //
    //  Nothing.

    $.addOutsideEvent = jq_addOutsideEvent;

    function jq_addOutsideEvent(event_name, outside_event_name) {
        // The "outside" event name.
        outside_event_name = outside_event_name || event_name + outside;

        // A jQuery object containing all elements to which the "outside" event is
        // bound.
        var elems = $(),
            // The "originating" event, namespaced for easy unbinding.
            event_namespaced =
                event_name + "." + outside_event_name + "-special-event";

        // Event: outside events
        //
        // An "outside" event is triggered on an element when its corresponding
        // "originating" event is triggered on an element outside the element in
        // question. See the <Default "outside" events> list for more information.
        //
        // Usage:
        //
        // > jQuery('selector').bind( 'clickoutside', function(event) {
        // >   var clicked_elem = $(event.target);
        // >   ...
        // > });
        //
        // > jQuery('selector').bind( 'dblclickoutside', function(event) {
        // >   var double_clicked_elem = $(event.target);
        // >   ...
        // > });
        //
        // > jQuery('selector').bind( 'mouseoveroutside', function(event) {
        // >   var moused_over_elem = $(event.target);
        // >   ...
        // > });
        //
        // > jQuery('selector').bind( 'focusoutside', function(event) {
        // >   var focused_elem = $(event.target);
        // >   ...
        // > });
        //
        // You get the idea, right?

        $.event.special[outside_event_name] = {
            // Called only when the first "outside" event callback is bound per
            // element.
            setup: function () {
                // Add this element to the list of elements to which this "outside"
                // event is bound.
                elems = elems.add(this);

                // If this is the first element getting the event bound, bind a handler
                // to document to catch all corresponding "originating" events.
                if (elems.length === 1) {
                    $(doc).bind(event_namespaced, handle_event);
                }
            },

            // Called only when the last "outside" event callback is unbound per
            // element.
            teardown: function () {
                // Remove this element from the list of elements to which this
                // "outside" event is bound.
                elems = elems.not(this);

                // If this is the last element removed, remove the "originating" event
                // handler on document that powers this "outside" event.
                if (elems.length === 0) {
                    $(doc).unbind(event_namespaced);
                }
            },

            // Called every time a "outside" event callback is bound to an element.
            add: function (handleObj) {
                var old_handler = handleObj.handler;

                // This function is executed every time the event is triggered. This is
                // used to override the default event.target reference with one that is
                // more useful.
                handleObj.handler = function (event, elem) {
                    // Set the event object's .target property to the element that the
                    // user interacted with, not the element that the "outside" event was
                    // was triggered on.
                    event.target = elem;

                    // Execute the actual bound handler.
                    old_handler.apply(this, arguments);
                };
            },
        };

        // When the "originating" event is triggered..
        function handle_event(event) {
            // Iterate over all elements to which this "outside" event is bound.
            $(elems).each(function () {
                var elem = $(this);

                // If this element isn't the element on which the event was triggered,
                // and this element doesn't contain said element, then said element is
                // considered to be outside, and the "outside" event will be triggered!
                if (this !== event.target && !elem.has(event.target).length) {
                    // Use triggerHandler instead of trigger so that the "outside" event
                    // doesn't bubble. Pass in the "originating" event's .target so that
                    // the "outside" event.target can be overridden with something more
                    // meaningful.
                    elem.triggerHandler(outside_event_name, [event.target]);
                }
            });
        }
    }
})(jQuery, document, "outside");
